package cn.tannn.ops.util;

import cn.tannn.jdevelops.exception.built.BusinessException;
import cn.tannn.ops.backup.model.SshRemoteFile;
import com.jcraft.jsch.*;
import lombok.extern.slf4j.Slf4j;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * ssh
 *
 * @author <a href="https://t.tannn.cn/">tan</a>
 * @version V1.0
 * @date 2024/9/10 上午9:52
 */
@Slf4j
public class SSHUtil {

    /**
     * 连接服务器
     *
     * @param user     服务器登录账号
     * @param password 服务器登录密码
     * @param host     服务器ip
     * @param port     服务器端口
     * @return Session
     */
    public static Session connectionServer(String user
            , String password
            , String host
            , int port) throws JSchException {
        JSch jsch = new JSch();
        Session session = jsch.getSession(user, host, port);
        session.setPassword(password);
        Properties config = new Properties();
        // 控制如何处理主机密钥验证 [当前：设置不验证主机公钥]
        // yes（默认）：严格检查模式
        // no：禁用检查
        // ask：询问用户
        config.put("StrictHostKeyChecking", "no");
        // 只使用密码认证，不使用 GSSAPI 和公钥
//        config.put("PreferredAuthentications", "password");
        session.setConfig(config);
        log.info("Establishing Connection {}  ...", host);
        session.connect();
        return session;
    }

    /**
     * 远程下载文件 - 下载到本地
     *
     * @param session    {@link SSHUtil#connectionServer(String, String, String, int)}
     * @param remoteFile 远程文件绝对路径
     * @param localFile  下载到本地的绝对路径 (d://xx/+(dbName + "_" + System.currentTimeMillis() + ".sql"))
     * @param remove     是否删除远程文件
     * @throws JSchException JSchException
     * @throws SftpException SftpException
     */
    public static void remoteDownload(Session session
            , String remoteFile
            , String localFile
            , boolean remove) throws JSchException, SftpException {
        ChannelSftp sftpChannel = null;
        try {
            sftpChannel = (ChannelSftp) session.openChannel("sftp");
            sftpChannel.connect();
            sftpChannel.get(remoteFile, localFile);
            if (remove) {
                // 删除远程备份文件
                sftpChannel.rm(remoteFile);
                log.debug("remote rm {}", remoteFile);
            }
        } catch (JSchException e) {
            log.error("ssh error", e);
            throw e;
        } catch (SftpException e) {
            log.error("sftp error", e);
            throw e;
        } finally {
            if (sftpChannel != null && sftpChannel.isConnected()) {
                sftpChannel.disconnect();
            }
        }
    }

    /**
     * 远程下载文件 - 变成流
     *
     * @param session    {@link SSHUtil#connectionServer(String, String, String, int)}
     * @param remoteFile 远程文件绝对路径
     * @param remove     是否删除远程文件
     * @throws JSchException JSchException
     * @throws SftpException SftpException
     */
    public static InputStream remoteDownload(Session session
            , String remoteFile
            , boolean remove) throws JSchException, SftpException {
        ChannelSftp sftpChannel = null;
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            sftpChannel = (ChannelSftp) session.openChannel("sftp");
            sftpChannel.connect();
            // 使用 ChannelSftp.get 方法下载文件到 InputStream
            ;
            sftpChannel.get(remoteFile, outputStream);
            // 将 ByteArrayOutputStream 转换为 InputStream
            InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
            if (remove) {
                // 删除远程备份文件
                sftpChannel.rm(remoteFile);
                log.debug("remote rm {}", remoteFile);
            }
            return inputStream;
        } catch (JSchException e) {
            log.error("ssh error", e);
            throw e;
        } catch (SftpException e) {
            log.error("sftp error", e);
            throw e;
        } catch (IOException e) {
            log.error("io error", e);
            throw new RuntimeException(e);
        } finally {
            if (sftpChannel != null && sftpChannel.isConnected()) {
                sftpChannel.disconnect();
            }
        }
    }

    /**
     * 远程删除文件
     *
     * @param session    {@link SSHUtil#connectionServer(String, String, String, int)}
     * @param remoteFile 远程文件绝对路径
     * @throws JSchException JSchException
     * @throws SftpException SftpException
     */
    public static void remoteRemove(Session session
            , String remoteFile) throws JSchException, SftpException {
        ChannelSftp sftpChannel = null;
        try {
            sftpChannel = (ChannelSftp) session.openChannel("sftp");
            sftpChannel.connect();
            // 删除远程备份文件
            sftpChannel.rm(remoteFile);
            log.debug("remote rm {}", remoteFile);
        } catch (JSchException e) {
            log.error("ssh error", e);
            throw e;
        } catch (SftpException e) {
            log.error("sftp error", e);
            throw e;
        } finally {
            if (sftpChannel != null && sftpChannel.isConnected()) {
                sftpChannel.disconnect();
            }
            disconnectServer(session);
        }
    }

    /**
     * 创建文件 , 创建之前会检查是否存在
     *
     * @param fileDir  文件路径
     * @param fileName 文件名
     */
    public static void createFile(Session session, String fileDir, String fileName) throws Exception {
        // Check if the file exists
        String filePath = fileDir + "/" + fileName;
        if (!doesFileExist(session, filePath)) {
            // If not, create the file
            // 先创建目录
            createDir(session, fileDir);
            createEmptyFile(session, filePath);
            log.info("File created: " + filePath);
        } else {
            log.info("File already exists: " + filePath);
        }
    }

    /**
     * 关闭服务器连接
     *
     * @param session session
     */
    public static void disconnectServer(Session session) {
        if (session != null && session.isConnected()) {
            session.disconnect();
        }
    }


    /**
     * 验证文件是否存在
     *
     * @param session  Session
     * @param filePath filePath[文件绝对路径]
     * @return boolean
     * @throws Exception Exception
     */
    public static boolean doesFileExist(Session session, String filePath) throws Exception {
        String command = "[ -f " + filePath + " ] && echo \"File exists\" || echo \"File does not exist\"";
        String result = executeCommand(session, command);
        return "File exists".equals(result.trim());
    }

    /**
     * 创建空文件
     *
     * @param session  Session
     * @param fileName file名
     * @throws Exception Exception
     */
    public static void createEmptyFile(Session session, String fileName) throws JSchException, IOException {
        String command = "touch " + fileName;
        executeCommand(session, command);
    }

    /**
     * 创建目录
     *
     * @param session Session
     * @param dir     目录路径
     * @throws Exception Exception
     */
    public static void createDir(Session session, String dir) {
        try {
            String command = "mkdir -p  " + dir;
            executeCommand(session, command);
        } catch (Exception e) {
            log.error("Error creating directory, path exists: {}", dir, e);
        }
    }

    /**
     * 执行 命令
     *
     * @param session Session
     * @param command 命令
     * @return 返回命令输出
     * @throws Exception Exception
     */
    private static String executeCommand(Session session, String command) throws JSchException, IOException {
        ChannelExec channel = null;
        try {
            channel = (ChannelExec) session.openChannel("exec");
            channel.setCommand(command);

            InputStream in = channel.getInputStream();
            channel.connect();

            byte[] tmp = new byte[1024];
            StringBuilder output = new StringBuilder();
            while (true) {
                while (in.available() > 0) {
                    int i = in.read(tmp, 0, 1024);
                    if (i < 0) {
                        break;
                    }
                    output.append(new String(tmp, 0, i));
                }
                if (channel.isClosed()) {
                    if (in.available() > 0) {
                        continue;
                    }
                    break;
                }
            }
            channel.disconnect();
            return output.toString();
        } catch (JSchException e) {
            throw e;
        } catch (IOException e) {
            throw e;
        } finally {
            if (channel != null && channel.isConnected()) {
                channel.disconnect();
            }
        }
    }

    /**
     * 获取远程服务器上的文件
     *
     * @param session   登录信息
     * @param remoteDir 指定远程目录
     * @return 文件
     */
    public static List<SshRemoteFile> getRemoteFileList(Session session, String remoteDir) throws JSchException, SftpException {
        List<SshRemoteFile> fileNames = new ArrayList<>();
        ChannelSftp sftpChannel = null;
        try {
            Channel channel = session.openChannel("sftp");
            channel.connect();
            sftpChannel = (ChannelSftp) channel;
            Vector<ChannelSftp.LsEntry> files = sftpChannel.ls(remoteDir);
            Collections.sort(files, (o1, o2) -> {
                long time1 = o1.getAttrs().getMTime();
                long time2 = o2.getAttrs().getMTime();
                return Long.compare(time2, time1);
            });
            for (ChannelSftp.LsEntry entry : files) {
                if (!entry.getAttrs().isDir()) {
                    SshRemoteFile sshRemoteFile = new SshRemoteFile();
                    sshRemoteFile.setFileName(entry.getFilename());
                    sshRemoteFile.setFileSize(entry.getAttrs().getSize());
                    sshRemoteFile.setLastModified(entry.getAttrs().getMtimeString());
                    sshRemoteFile.setLastModifiedSecond(entry.getAttrs().getMTime());
                    sshRemoteFile.setPath(remoteDir);
                    fileNames.add(sshRemoteFile);
                }
            }
        } catch (Exception e) {
            throw e;
        } finally {
            disconnectServer(session);
            if (sftpChannel != null && sftpChannel.isConnected()) {
                sftpChannel.exit();
            }
        }
        return fileNames;
    }


    /**
     * 替换远程文件
     * @param session 服务器连接
     * @param remoteFilePath 远程文件路径
     * @param localFilePath 本地文件路径
     * @return message
     */
    public static String replaceFile(Session session,
                                     String remoteFilePath, String localFilePath) {
        ChannelSftp channelSftp = null;

        try {
            channelSftp = (ChannelSftp) session.openChannel("sftp");
            channelSftp.connect();
            // 上传本地文件以替换远程文件
            channelSftp.put(localFilePath, remoteFilePath, ChannelSftp.OVERWRITE);
            log.info("域名证书替换成功,remoteFilePath:{}",remoteFilePath);
            return "域名证书替换成功";
        } catch (Exception e) {
            throw new BusinessException("域名证书替换替换失败", e);
        } finally {
//            disconnectServer(session);
            if (channelSftp != null && channelSftp.isConnected()) {
                channelSftp.exit();
            }
        }
    }
}
